/*! \ingroup qgetopt
 * \class OptManager
 * \attention This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * \attention This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * \attention You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307  USA
 *
   \author    Graeme
   \date Copyright (C) 2008-9  Graeme Foster
 */
#include "optManager.h"
/*! \ingroup qgetopt
    \internal
*/
OptManager * OptManager::_instancePtr=0;


/*****************************************************************************/
/* Public Methods
 *****************************************************************************/
/*! This method will locate the switch specified by \em opt and then return the
    list of arguments associate with the switch.
    If the switch hasn't been used then the method will return an empty list
 */
QStringList OptManager::getSwitchDetails(QString opt)
{
   for (int i = 0; i < _activeSwitches.size(); ++i)
   {
      if (_activeSwitches[i]->name() == opt)
         return _activeSwitches[i]->argList();
   }
   return QStringList();
}

/*! This method will add an argument described by \em args to the application.
    This argument must be supplied when the program runs.
 */
void OptManager::addAppArgs (QString args)
{
   QStringList argParts = args.split(" ", QString::SkipEmptyParts);
   for (int i = 0; i < argParts.size(); ++i)
   {
      OptArgument * oa = new OptArgument(argParts[i]);
      if (oa->isOptional())
         throw QObject::tr("Program arguments cannot be optional");
      _argList.append(oa);
   }
}

/*! This method will add a long switch to the program with the name of \em longName
    and may optionally have a short switch of \em abbrev.

    If the switch is provided when the program is run
    then the method \em act will be run at the start of the program,
    it is actually run within the parse() method. Additionally, if provided, the \em exit method
    will be run when the program completes, this is done from within the close() method.

    The optional parameter \em conclude determines whether the switch will stop the program once
    its method has been performed. By default the \em --help switch is a conclude switch,
    so it displays the help screen and then the program will stop.
 */
OptSwitch * OptManager::addSwitch(QString longName
                                 ,ActionFuncPtr actionMethod
                                 ,QChar abbrev
                                 ,QString args
                                 ,CloseFuncPtr exitMethod
                                 ,switchStyle style
                                 )
{
   if (!uniqueAbbrev(abbrev))
      throw QString("Duplicate switch -")+abbrev+" used.";
   OptSwitch * sw = new OptSwitch(abbrev, longName, args, actionMethod, exitMethod, style);
   _switchList.append(sw);
   return sw;
}

/*! This method will extract details from the commandline arguments passed in.
    It expects the first element of argv to be the name of the application.
    All subsequent elements of argv will either be a switch or argument.
 */
QString OptManager::parse(int argc, char *argv[], bool quiet)
{
   QStringList argQueue;
   OptSwitch * sw = 0;
   for (int i =1; i < argc; ++i)
   {
      if (isSwitch(argv[i]))
      {
         if (!parseArgument(sw, argQueue, true))
            _activeSwitches.removeLast(); // remove last switch because there is an argument error
         sw = parseSwitch(argv[i]);
      }
      else
      {
         argQueue.push_back(argv[i]);
      }
   } // end of looping through each argument that was passed in.
   if (!parseArgument(sw, argQueue, false))
      _activeSwitches.removeLast(); // remove last switch because there is an argument error
   // Ready to run the code that is triggered by the switches.
   execute();

   QString notices;
   notices = getWarnings() + getErrors();
   if (!quiet)
   {
      QTextStream console(stdout);
      console << notices;
      console.flush();
      if (_error.size() > 0)
         exit (-1);
   }
   return notices;
}

/*! This method will ensure that all closing methods (if any) are called. For example a switch
    that automates logging to a file may require an exit method to close the file, this method
    can be setup as the \em exit parameter of the addSwitch() method.
 */
void OptManager::close()
{
   for (int i = 0; i< _activeSwitches.size(); ++i)
      _activeSwitches[i]->close();
}

/*! This method will return all the error messaegs in a single string each separated by a newline */
QString OptManager::getErrors()
{
   QString errors;
   for (int i = 0; i < _error.size(); ++i)
      errors += "Error: " + _error[i] + "\n";
   return errors;
}

/*! This method will return all the warning messaegs in a single string each separated by a newline */
QString OptManager::getWarnings()
{
   QString warnings;
   for (int i = 0; i< _warning.size(); ++i)
      warnings += "Warning: " + _warning[i] + "\n";
   return warnings;
}

/*! This method will return a description of the selected switch */
QString OptManager::switchDumpTemplate(int i)
{
   return ((i>=0) && (i<_switchList.size()))? _switchList[i]->help():"";
}

/*! This method will return as a QString the details of each switch that has been selected by the user.
    It will display the detail one switch per line.
 */
QString OptManager::switchDump()
{
   QString details;
   for (int i = 0; i < _activeSwitches.size(); ++i)
   {
      details += "   " + _activeSwitches[i]->dump() + "\n";
   }
   return details;
}

/*! This method will return as a QString a list of each argument that been provided by the user
    specifically for the application. It will display the arguments seperated by a space.
 */
QString OptManager::appArgDump()
{
   QString details;
   for (int i = 0; i < _actualAppArgList.size(); ++i)
   {
      details +=  " " + _actualAppArgList[i];
   }
   return details;
}


/*****************************************************************************/
/* Private Methods
 *****************************************************************************/

/*! \internal This will instantiate an instance of the OptManager class.
  * By default it adds a single switch the --help switch that
  * is used to automatically display information about the program
  */
OptManager::OptManager(QFileInfo appFullName)
{
   _appName = appFullName.baseName();
   _path    = appFullName.path();
   _initMethodPtr = 0;
   _cmdLine = "";
   _error.clear();
   QString detail;
   detail = QObject::tr("Display this information");
   addSwitch("help", help, 'h',"",0,immediateStop)->addDescription(detail);
}

/*! This class is a singleton class and so the constructor is private, because of that an object
    cannot be created via new instead it will be returned by this method.

    The first time that this method is called the \em appFullName needs to be provided
    for all subsequent calls it is not needed because the instance will just be returned.
 */
OptManager * OptManager::getOptMgr(QString appFullName)
{
   if (_instancePtr==0)
      _instancePtr = new OptManager(appFullName);
   return _instancePtr;
}


/*! \internal This will first look for an then execute any method of a conclude switch
    If there was a conclude switch then control will be returned to the calling method
    Assuming that there was nine then the application init method is run followed by
    the execute method of each switch.
 */
void OptManager::execute()
{
   for (int i = 0; i< _activeSwitches.size(); ++i)
   {
      if (_activeSwitches[i]->finishProcessing())
      {
         _activeSwitches[i]->execute();
         _activeSwitches[i]->close();
         exit(0);
      }
   }
   if (_error.size() == 0)
   {
      if (_initMethodPtr)
         _initMethodPtr(_actualAppArgList);
      for (int i = 0; i< _activeSwitches.size(); ++i)
      {
         _activeSwitches[i]->execute();
      }
   }
}

/*! \internal Uses the private methods isNumber(), isLongSwitch() and isShortSwitch() to
    determine if the parampassed in is a switch. It will first call isNumber() and return
    false if true is returned this is necessary to allow a negative number to be passed in as
    an argument.
 */
bool OptManager::isSwitch(QString param)
{
   if (isNumber(param))
      return false;
   return (isLongSwitch(param) || isShortSwitch(param));
}

/*!
    \internal This is used by isSwitch() to see if \em param is a short switch.
 */
bool OptManager::isShortSwitch(QString param)
{
   return (!isLongSwitch(param) && param.startsWith("-"));
}

/*!
    \internal This is used by isSwitch() to see if \em param is a long switch.
 */
bool OptManager::isLongSwitch(QString param)
{
   return param.startsWith("--");
}

/*!
    \internal This is used by isSwitch() to see if \em param is a number.
    If the option is a valid number then it will be treated as an argument
    and not a switch. This is neccessary to allow for arguments of negative numbers.
 */
bool OptManager::isNumber(QString param)
{
   int decimalCnt = 0;
   for(int i = 0; i<param.length(); ++i)
   {
      if (param[i].isNumber())
         continue;
      if (i == 0 && param[i]=='-')
         continue;
      if (param[i] == '.' && decimalCnt == 0)
      {
         decimalCnt = 1;
         continue;
      }
      return false;
   }
   return true;
}

/*! This method will return true if the character passed in is null
    or if has not yet been used as a short switch.
    Otherwise it will return false.
 */
bool OptManager::uniqueAbbrev(QChar abbrev)
{
   if (abbrev.isNull())
      return true;
   for (int i =0; i < _switchList.size(); ++i)
   {
      if (_switchList[i]->abbrev() == abbrev)
         return false;
   }
   return true;
}

/*!
    \internal This receives a QStringList \em argQueue of arguments which will be assigned to either the
    program or the switch \em sw that is passed in. If \em more is set to true then there are
    more switches waiting to be processed and so all the arguments should belong to a switch.
    Otherwise the program arguments will be processed first followed by the switch arguments.

    If there is an error condition then the error message will be placed on the \em error member variable.

    If the arguments for the switch are in error then false will be returned otherwise true is returned
    (even if there is an error condition).
    The reason for this is that if the switch is in error then it will be removed and processing will continue
    to allow for the "discovery" of a valid conclude switch, such as --help. This means that even if the other
    switches are in error the help screen will be displayed.
 */
bool OptManager::parseArgument(OptSwitch * sw, QStringList &argQueue, bool more)
{
   // This method returns false if a switch's arguments are in error. For other errors true is returned.
   if (!more)
   {
      // If there is a completer switch then grab the arguments and return
      if(sw != 0 && sw->finishProcessing())
      {
         return parseSwitchArgument(sw,argQueue);
      }
      // Remove the program arguments before sorting out any arguments for the switch
      if (_argList.size() > argQueue.size())
      {
         _error.append(QObject::tr("Insufficient program arguments. %1 expected %2 provided.")
                     .arg(_argList.size())
                     .arg(argQueue.size())
                     );
         argQueue.clear();
         return true;
      }
      for (int i = 0; i < _argList.size(); ++i)
      {
         _actualAppArgList.prepend(argQueue.takeLast());
      }
   } // end of if there are no more switches to be processed
   if (sw == 0)
   {
      if (argQueue.size() > 0)
      {
         _error.append(QObject::tr("No switch defined for the argument %1.")
                     .arg(argQueue[0])
                     );
         argQueue.clear();
      }
      return true;
   }
   return parseSwitchArgument(sw,argQueue);
}

/*!
    \internal This is used by parseArgument() to add the arguments held in \em argQueue to the switch \em sw.

    If there is an error condition then the error message will be placed on the \em error member variable.

    If the arguments for the switch are in error then false will be returned otherwise true is returned
 */
bool OptManager::parseSwitchArgument(OptSwitch * sw, QStringList &argQueue)
{
   if (argQueue.size() > sw->argCnt())
   {
      _error.append(QObject::tr("Too many arguments for the switch --%1. %2 expected %3 provided.")
                  .arg(sw->name())
                  .arg(sw->argCnt())
                  .arg(argQueue.size())
                  );
      argQueue.clear();
      return false;
   }
   if (argQueue.size() < sw->mArgCnt())
   {
      _error.append(QObject::tr("Insufficient arguments for the switch --%1. %2 expected %3 provided.")
                  .arg(sw->name())
                  .arg(sw->mArgCnt())
                  .arg(argQueue.size())
                  );
      argQueue.clear();
      return false;
   }
   if (sw->actualArgCnt()>0)
   {
      //This means that the switch has already been parsed with arguments
      if (sw->actualArgCnt() != argQueue.size())
      {
         _error.append(QObject::tr("The switch --%1 was duplicated with different arguments.")
                     .arg(sw->name())
                     );
         argQueue.clear();
         return false;
      }
      for (int i = 0; i < sw->actualArgCnt(); ++i)
      {
         if (sw->getArg(i) != argQueue.at(i))
         {
            _error.append(QObject::tr("The switch --%1 was duplicated with different arguments.")
                        .arg(sw->name())
                        );
            argQueue.clear();
            return false;
         }
      }
      // the Arguments are identical so just return
      return true;
   }
   while (!argQueue.isEmpty())
   {
      sw->addActualArg(argQueue.takeFirst());
   }
   return true;
}

/*!
    \internal This is used by parse to create an instance of a switch from \em opt.
    This method will use parseShortSwitchList() and parseLongSwitch() methods to create the instance.
    It will check that \em opt is a valid switch before calling these helper methods.
 */
OptSwitch * OptManager::parseSwitch(QString opt)
{
   if (isShortSwitch(opt))
      return parseShortSwitchList(opt);
   if (isLongSwitch(opt))
      return parseLongSwitch(opt);
   else // Should never get here because isSwitch() should always be called before this method
      return 0;
}

/*!
    \internal This is used by parseSwitch() to create the short switches from \em opt.
    The method expects a list of valid short switches and it will use parseShortSwitch
    for each character in the list. Each switch will be held in the activeSwitches member variable.
    The instance of the last switch will be returned.

    The instance of the last short switch is returned so that any arguments that follow can be associated
    with this OptSwitch object by the parseArgument() method.
 */
OptSwitch * OptManager::parseShortSwitchList(QString opt)
{
   OptSwitch * sw=0;
   QStringList emptyArgList;
   for (int i =1; i<opt.size(); ++i)
   {
      sw = parseShortSwitch(opt.at(i));
      if (i < opt.size()-1)
         if(!parseArgument(sw,emptyArgList,true))
            _activeSwitches.removeLast(); // remove last switch because there is an argument error
   }
   return sw;
}

/*!
    \internal This is used by parseShortSwitchList() which will pass in a single character, this will be checked
    against the list of known short switches and if it exists then an instance will be created and
    added to the activeSwitches member variable. The instance will be returned.

    All switches that have been set up except the last will be verified that they don't require
    arguments by calling parseArgument().
    If they do require an argument then the error message will be set and the switch removed from the list.
 */
OptSwitch * OptManager::parseShortSwitch(QChar opt)
{
   QListIterator <OptSwitch *> i(_switchList);
   while (i.hasNext())
   {
      OptSwitch * sw = i.next();
      QString abbr = sw->abbrev();
      if (abbr == opt)
      {
         if (!_activeSwitches.contains(sw))
            _activeSwitches.append(sw);
         return sw;
      }
   }
   _warning.append(QObject::tr("Unknown switch %1").arg(opt));
   return 0;
}

/*!
    \internal This is used by parseSwitch() which will pass in a potental long switch, this will be checked
    against the list of known switches and if it exists then an instance will be created and
    added to the activeSwitches member variable. The instance will be returned.
 */
OptSwitch * OptManager::parseLongSwitch(QString opt)
{
   opt.remove(0,2);
   QListIterator <OptSwitch *> i(_switchList);
   while (i.hasNext())
   {
      OptSwitch * sw = i.next();
      QString option = sw->name();
      if (option == opt)
      {
         if (!_activeSwitches.contains(sw))
            _activeSwitches.append(sw);
         return sw;
      }
   }
   _warning.append(QObject::tr("Unknown switch %1").arg(opt));
   return 0;
}

/*!
    \internal This is used by the static method help() and does all the necessary formatting.
 */
QString OptManager::pHelp()
{
   QString underline(_appName.size(),'=');
   QString desc;
   QString extra;
   QString detail;
   QString arguments;
   desc = "\n"+ _appName + "\n"+ underline + "\nusage: "+ _appName + " [-";
   if (!_description.isEmpty())
   {
      detail += "\n" + _description + "\n";
   }
   for (int i =0; i < _switchList.size(); ++i)
   {
      if (_switchList[i]->finishProcessing())
         extra += "\n       "+ _appName + _switchList[i]->brief();
      else
         if (!_switchList[i]->abbrev().isNull())
            desc += _switchList[i]->abbrev();
      detail += "\n   " + _switchList[i]->help();
   }
   for (int i = 0 ; i < _argList.size(); ++i)
   {
      arguments += " " + _argList[i]->help();
   }
   return desc + "]" + arguments + extra + detail + "\n\n";
}
